# coding=utf-8
import bytecode

from frameobject import Block, FrameObject
from functionobject import FunctionObject, MethodObject


class Interpreter(object):
    def __init__(self):
        self.frame = None
        self.builtins = {
            "True": True,
            "False": False,
            "None": None,
            "len": len,
            "int": int,
            "object": object,
            "str": str,
            "list": list,
            "dict": dict,
            "isinstance": isinstance,
            "type": type,
        }

    def build_frame(self, f, args, op_arg):
        if isinstance(f, FunctionObject):
            frame = FrameObject(func=f, args=args, op_arg=op_arg)
            frame.sender = self.frame
            self.frame = frame
        elif isinstance(f, MethodObject):
            func = f.func
            if args is not None:
                value = func(*args)
            else:
                value = func()
            self.frame.push(value)
        elif callable(f):
            if args is not None:
                value = f(*args)
            else:
                value = f()
            self.frame.push(value)

    def leave_frame(self, ret_value):
        if not self.frame.sender:
            self.frame = None
            return

        frame = self.frame
        self.frame = frame.sender
        self.frame.push(ret_value)

    def run(self, codes):
        self.frame = FrameObject(codes)

        while self.frame.has_more_codes():
            op_code = self.frame.get_op_code()

            has_argument = (op_code & 0xFF) >= bytecode.HAVE_ARGUMENT
            op_arg = -1
            if has_argument:
                op_arg = self.frame.get_op_arg()

            if op_code == bytecode.POP_TOP:
                self.frame.pop()
            elif op_code == bytecode.LOAD_CONST:
                tmp = self.frame.consts[op_arg]
                self.frame.stack.append(tmp)
            elif op_code == bytecode.LOAD_NAME:
                v = self.frame.get_name(op_arg)

                # 尝试从 local 空间里面获取
                w = self.frame.get_local(v)
                if w is not None:
                    self.frame.push(w)
                    continue

                # 尝试从 global 空间里面获取
                w = self.frame.globals.get(v, None)
                if w is not None:
                    self.frame.push(w)
                    continue

                # 尝试从 builtins 空间里面获取
                w = self.builtins.get(v, None)
                if w is not None:
                    self.frame.push(w)
                    continue

                # 都获取不到，则存入 None
                self.frame.push(None)
            elif op_code == bytecode.LOAD_FAST:
                fast = self.frame.fast_locals[op_arg]
                self.frame.push(fast)
            elif op_code == bytecode.LOAD_GLOBAL:
                v = self.frame.get_name(op_arg)
                w = self.frame.globals.get(v, None)
                if w:
                    self.frame.push(w)
                    continue

                w = self.builtins.get(v, None)
                if w:
                    self.frame.push(w)
                    continue

                self.frame.push(None)
            elif op_code == bytecode.LOAD_ATTR:
                v = self.frame.pop()
                w = self.frame.names[op_arg]
                attr = getattr(v, w)
                if attr is not None:
                    # TODO: Fix Potential Bug.
                    # 如果 attr 是函数对象
                    if callable(attr):
                        method = MethodObject(attr, v)
                        self.frame.push(method)
                    # 如果 attr 是属性
                    else:
                        self.frame.push(attr)
                else:
                    self.frame.push(None)
            elif op_code == bytecode.LOAD_LOCALS:
                self.frame.push(self.frame.locals)
            elif op_code == bytecode.STORE_NAME:
                key = self.frame.get_name(op_arg)
                value = self.frame.pop()
                self.frame.set_local(key, value)
            elif op_code == bytecode.STORE_FAST:
                # TODO: Fix Potential Bug.
                while len(self.frame.fast_locals) <= op_arg:
                    self.frame.fast_locals.append(None)
                self.frame.fast_locals[op_arg] = self.frame.pop()
            elif op_code == bytecode.STORE_GLOBAL:
                key = self.frame.get_name(op_arg)
                self.frame.globals[key] = self.frame.pop()
            elif op_code == bytecode.STORE_SUBSCR:
                index = self.frame.pop()
                container = self.frame.pop()
                new_item = self.frame.pop()
                container[index] = new_item
            elif op_code == bytecode.STORE_MAP:
                w = self.frame.pop()
                u = self.frame.pop()
                v = self.frame.top()
                v[w] = u
            elif op_code == bytecode.STORE_ATTR:
                u = self.frame.pop()
                v = self.frame.names[op_arg]
                w = self.frame.pop()
                setattr(u, v, w)
            elif op_code == bytecode.BINARY_SUBSCR:
                v = self.frame.pop()
                w = self.frame.pop()
                self.frame.push(w[v])
            elif op_code == bytecode.PRINT_ITEM:
                v = self.frame.pop()
                print v,
            elif op_code == bytecode.PRINT_NEWLINE:
                print
            elif op_code == bytecode.BINARY_ADD:
                v = self.frame.pop()
                w = self.frame.pop()
                self.frame.push(v + w)
            elif op_code == bytecode.BINARY_SUBSTRACT:
                v = self.frame.pop()
                w = self.frame.pop()
                self.frame.push(w - v)
            elif op_code == bytecode.BINARY_MULTIPLY:
                v = self.frame.pop()
                w = self.frame.pop()
                self.frame.push(w * v)
            elif op_code == bytecode.MAKE_FUNCTION:
                v = self.frame.pop()
                fo = FunctionObject(v)
                fo.globals = self.frame.globals
                if op_arg > 0:
                    args = [None for _ in range(op_arg)]
                    while op_arg > 0:
                        op_arg -= 1
                        args[op_arg] = self.frame.pop()
                    fo.defaults = args
                self.frame.push(fo)
            elif op_code == bytecode.CALL_FUNCTION:
                args = None
                if op_arg > 0:
                    na = op_arg & 0xFF
                    nk = op_arg >> 8
                    arg_cnt = na + 2 * nk
                    args = [None for _ in range(arg_cnt)]
                    while arg_cnt > 0:
                        arg_cnt -= 1
                        args[arg_cnt] = self.frame.pop()

                self.build_frame(self.frame.pop(), args, op_arg)
            elif op_code == bytecode.RETURN_VALUE:
                ret = self.frame.pop()
                self.leave_frame(ret)
                if not self.frame:
                    return
            elif op_code == bytecode.COMPARE_OP:
                w = self.frame.pop()
                v = self.frame.pop()

                if op_arg == bytecode.COMPARE.GREATER:
                    self.frame.push(v > w)
                elif op_arg == bytecode.COMPARE.LESS:
                    self.frame.push(v < w)
                elif op_arg == bytecode.COMPARE.EQUAL:
                    self.frame.push(v == w)
                elif op_arg == bytecode.COMPARE.NOT_EQUAL:
                    self.frame.push(v != w)
                elif op_arg == bytecode.COMPARE.GREATER_EQUAL:
                    self.frame.push(v >= w)
                elif op_arg == bytecode.COMPARE.LESS_EQUAL:
                    self.frame.push(v <= w)
                elif op_arg == bytecode.COMPARE.IN:
                    self.frame.push(v in w)
                elif op_arg == bytecode.COMPARE.NOT_IN:
                    self.frame.push(v not in w)
                elif op_arg == bytecode.COMPARE.IS:
                    self.frame.push(v is w)
                elif op_arg == bytecode.COMPARE.IS_NOT:
                    self.frame.push(v is not w)
                else:
                    print("Error: Unrecognized compare op %d\n", op_arg)
            elif op_code == bytecode.DELETE_SUBSCR:
                index = self.frame.pop()
                container = self.frame.pop()
                del container[index]
            elif op_code == bytecode.POP_JUMP_IF_FALSE:
                v = self.frame.pop()
                if not v:
                    self.frame.pc = op_arg
            elif op_code == bytecode.JUMP_FORWARD:
                self.frame.pc += op_arg
            elif op_code == bytecode.JUMP_ABSOLUTE:
                self.frame.pc = op_arg
            elif op_code == bytecode.SETUP_LOOP:
                block = Block(op_code, self.frame.pc + op_arg, self.frame.stack_level())
                self.frame.loop_stack.append(block)
            elif op_code == bytecode.POP_BLOCK:
                block = self.frame.loop_stack.pop()
                while self.frame.stack_level() > block.level:
                    self.frame.pop()
            elif op_code == bytecode.BREAK_LOOP:
                block = self.frame.loop_stack.pop()
                while self.frame.stack_level() > block.level:
                    self.frame.pop()
                self.frame.pc = block.target
            elif op_code == bytecode.BUILD_TUPLE:
                l = [None for _ in range(op_arg)]
                while op_arg > 0:
                    op_arg -= 1
                    l[op_arg] = self.frame.pop()
                t = tuple(l)
                self.frame.push(t)
            elif op_code == bytecode.BUILD_LIST:
                l = [None for _ in range(op_arg)]
                while op_arg > 0:
                    op_arg -= 1
                    l[op_arg] = self.frame.pop()
                self.frame.push(l)
            elif op_code == bytecode.BUILD_CLASS:
                v = self.frame.pop()
                u = self.frame.pop()
                w = self.frame.pop()
                v = type(w, u, v)
                self.frame.push(v)
            elif op_code == bytecode.BUILD_MAP:
                v = dict()
                self.frame.push(v)
            elif op_code == bytecode.GET_ITER:
                v = self.frame.pop()
                self.frame.push(iter(v))
            elif op_code == bytecode.FOR_ITER:
                v = self.frame.top()
                try:
                    self.frame.push(v.next())
                except StopIteration:
                    self.frame.pc += op_arg
                    self.frame.pop()
            elif op_code == bytecode.UNPACK_SEQUENCE:
                v = self.frame.pop()
                while op_arg > 0:
                    op_arg -= 1
                    self.frame.push(v[op_arg])
            else:
                print("not support op_code:", op_code)
